package suncertify.gui;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import suncertify.application.Application;
import suncertify.application.ApplicationMode;
import suncertify.application.Log;
import suncertify.application.ServerApplicationFrameInterface;
import suncertify.application.Strings;
import suncertify.application.exceptions.DatabaseFileNotFoundException;
import suncertify.application.exceptions.DatabaseFileNotReadableException;
import suncertify.application.exceptions.DatabaseFileNotWritableException;
import suncertify.application.exceptions.DatabaseFileWrongTypeException;
import suncertify.application.exceptions.ServerStartException;
import suncertify.application.exceptions.ServerStopException;
import suncertify.gui.panels.FileLocationPanel;
import suncertify.gui.panels.NumericValuePanel;

/**
 * Provides the server application frame window that consists of the following user interface components:
 * <ol>
 * <li>Fixed size window that shows the application's name in the title bar. The window is shown in the center of the
 * screen.</li>
 * <li>Text field and file browse button that allow the user to enter or browse the path to the database file.</li>
 * <li>A text field to enter the port number used by the server to communicate with the client application(s).</li>
 * <li>The start, stop and terminate buttons.</li>
 * </ol>
 * 
 * <p>
 * This class must be constructed with an implementation of the {@link ServerApplicationFrameInterface}. The main
 * objective of this class is create all the components of the view like buttons, text fields, setting the layout of the
 * components, assigning texts, tool tips, and mnemonic keys. The class delegates all its user interactions to the
 * implementor of the {@link ServerApplicationFrameInterface}.
 * 
 * <p>
 * All user interactions with the application (like starting or stopping the the server) fire the
 * {@link #actionPerformed(ActionEvent)} function. Each UI component that performs an action (e.g. the start button) is
 * assigned a unique string defined by the internal {@link ActionCommands} enumerator. The
 * {@link #actionPerformed(ActionEvent)} function filters the action events based on the enumerator value and calls the
 * corresponding action function, e.g. {@link #executeStartServerCommand(String, int)} and
 * {@link #executeStopServerCommand()}.
 * 
 * <p>
 * The action functions delegate the call to the corresponding {@link ServerApplicationFrameInterface} functions, for
 * each user interaction there is a corresponding method in the interface e.g. the
 * {@link ServerApplicationFrameInterface#startServer(String, int)} method is called when the user clicks the start
 * button. Any raised exceptions are caught in the {@link #actionPerformed(ActionEvent)} function and are shown using
 * the {@link DesktopGui#showErrorMessage(String, String)}.
 * 
 * <p>
 * This class is shown if the application is started with the {@link ApplicationMode#SERVER} command line parameter, one
 * instance of this calls should be created in this mode.
 * 
 */
public class ServerApplicationFrame extends JFrame implements ActionListener, WindowListener {

    /**
     * Automatically generated serialization version ID.
     */
    private static final long serialVersionUID = 1133101044766448069L;

    /**
     * Boolean variable that reflects the state of the server. True means the server is start, false means the server is
     * stopped.
     */
    private boolean isServerStarted = false;

    /**
     * Declares the start button.
     */
    private JButton startButton;

    /**
     * Declares the stop button.
     */
    private JButton stopButton;

    /**
     * Declares the file location text field and label panel.
     */
    private FileLocationPanel filePanel;

    /**
     * Declares the port number label and field panel.
     */
    private NumericValuePanel portPanel;

    /**
     * Reference to the interface implementation used to perform the user interactions.
     */
    private ServerApplicationFrameInterface serverApplication;

    /**
     * Constructs the class with the provided interface implementation which is stored in the {@link #serverApplication}
     * variable. The class listens to window events by calling {@link Window#addWindowListener(WindowListener)}. The
     * constructor builds the complete user interface by calling {@link #buildPanels()}. The initial state of the server
     * is stopped, the user interface components are updated to reflect the server's state by calling
     * {@link #refreshControlsState()}. The server frame is positioned in the center of the screen. The constructor does
     * now show the frame, the caller must call the {@link Window#setVisible(boolean)} first.
     * 
     * @param serverApplication
     *            Implementation of the {@link ServerApplicationFrameInterface}.
     */
    public ServerApplicationFrame(ServerApplicationFrameInterface serverApplication) {

	this.serverApplication = serverApplication;

	this.addWindowListener(this);

	this.setTitle(Strings.CAP_SERVER_TITLE);
	this.setContentPane(this.buildPanels());
	this.refreshControlsState();

	this.pack();

	this.setLocation(DesktopGui.getCenterPoint(this.getWidth(), this.getHeight()));
	this.setResizable(false);
	this.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
	this.getRootPane().setDefaultButton(this.startButton);
    }

    /**
     * Creates the main {@link JPanel} that contains the {@link FileLocationPanel}, {@link NumericValuePanel} and the
     * panel created by {@link #buildButtonPanel()}.
     * 
     * @return The applications main panel.
     */
    private JPanel buildPanels() {

	JPanel rootPanel = new JPanel();
	rootPanel.setLayout(new BorderLayout());

	JPanel topPanel = new JPanel();
	topPanel.setLayout(new BorderLayout());

	this.filePanel = new FileLocationPanel(serverApplication.getDatabaseFilePath(), this.serverApplication
		.getUserCurrentDirectory());

	this.portPanel = new NumericValuePanel(this.serverApplication.getPortNumber(), Strings.CAP_PORT,
		Application.MAXIMUM_PORT_VALUE, false, KeyEvent.VK_P, Strings.TIP_PORT_SERVER);

	topPanel.add(this.filePanel, BorderLayout.NORTH);
	topPanel.add(this.portPanel, BorderLayout.SOUTH);

	rootPanel.add(topPanel, BorderLayout.NORTH);
	rootPanel.add(this.buildButtonPanel(), BorderLayout.SOUTH);

	return rootPanel;
    }

    /**
     * Creates the panel that contains the start, stop and terminate buttons. Each button is assigned a string, a tool
     * tip, a mnemonic key and an {@link ActionCommands} enumeration that uniquely identifies the button's action, the
     * start button is assigned the {@link ActionCommands#START_SERVER}, the stop button is assigned the
     * {@link ActionCommands#STOP_SERVER} and the terminate button is assigned the
     * {@link ActionCommands#TERMINATE_SERVER}.
     * 
     * @return The panel with the three buttons.
     */
    private JPanel buildButtonPanel() {

	this.startButton = new JButton(Strings.CAP_START);
	this.startButton.addActionListener(this);
	this.startButton.setActionCommand(ActionCommands.START_SERVER.toString());
	this.startButton.setToolTipText(Strings.TIP_SERVER_START);
	this.startButton.setMnemonic(KeyEvent.VK_S);

	stopButton = new JButton(Strings.CAP_STOP);
	this.stopButton.addActionListener(this);
	this.stopButton.setActionCommand(ActionCommands.STOP_SERVER.toString());
	this.stopButton.setToolTipText(Strings.TIP_SERVER_STOP);
	this.stopButton.setMnemonic(KeyEvent.VK_O);

	JButton terminateButton = new JButton(Strings.CAP_TERMINATE);
	terminateButton.addActionListener(this);
	terminateButton.setActionCommand(ActionCommands.TERMINATE_SERVER.toString());
	terminateButton.setToolTipText(Strings.TIP_SERVER_TERMINATE);
	terminateButton.setMnemonic(KeyEvent.VK_T);

	JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
	buttonPanel.add(this.startButton);
	buttonPanel.add(this.stopButton);
	buttonPanel.add(terminateButton);

	return buttonPanel;
    }

    /**
     * Updates the enabled state of the start button, stop button, file panel and port panel according to the value of
     * the {@link #isServerStarted} variable as follows:
     * <ol>
     * <li>If {@link #isServerStarted} is true then the above mentioned components are disabled except the stop button
     * is enabled.</li>
     * <li>If {@link #isServerStarted} is false then the above mentioned components are enabled except the stop button
     * is disabled.</li>
     * </ol>
     */
    private void refreshControlsState() {

	if (this.isServerStarted) {
	    this.startButton.setEnabled(false);
	    this.stopButton.setEnabled(true);
	    this.filePanel.setEnableState(false);
	    this.portPanel.setEnabledState(false);

	} else {
	    this.startButton.setEnabled(true);
	    this.stopButton.setEnabled(false);
	    this.filePanel.setEnableState(true);
	    this.portPanel.setEnabledState(true);
	}
    }

    /**
     * Called by swing when one of the the start, stop or terminate buttons is clicked. The clicked button is identified
     * by the {@link ActionEvent} parameter that contains the {@link ActionCommands} enumeration assigned in
     * {@link #buildButtonPanel()}. The function calls one of these functions
     * {@link #executeStartServerCommand(String, int)}, {@link #executeStopServerCommand()} or
     * {@link #executeTerminateServerCommand()} based on the clicked button. Any raised exceptions are caught and shown
     * using {@link DesktopGui#showErrorMessage(String, String)} or
     * {@link DesktopGui#showPerformedActionErrorMessage(Exception)}.
     * 
     * @param e
     *            See {@link ActionEvent}
     * 
     */
    public void actionPerformed(ActionEvent e) {

	try {
	    if (e.getActionCommand().equalsIgnoreCase(ActionCommands.START_SERVER.toString())) {

		// check if the database file is correct
		String databaseFilePath = this.filePanel.getDatabaseFilePath();
		this.serverApplication.isDatabaseFileValid(databaseFilePath);

		// check if the port number is empty.
		if (this.portPanel.isEmpty()) {
		    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_INVALID_PORT_NUMBER);
		    return;
		}

		int port = this.portPanel.getValue();

		if (!this.serverApplication.isPortNumberValid(port)) {
		    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_INVALID_PORT_NUMBER);
		    return;
		}

		this.executeStartServerCommand(databaseFilePath, port);

	    } else if (e.getActionCommand().equalsIgnoreCase(ActionCommands.STOP_SERVER.toString())) {

		this.executeStopServerCommand();
	    } else {

		this.executeTerminateServerCommand();
	    }
	} catch (DatabaseFileNotFoundException e1) {
	    Log.logInfo("ServerApplicationFrame.actionPerformed() caught DatabaseFileNotFoundException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_NOT_FOUND);

	} catch (DatabaseFileWrongTypeException e2) {
	    Log.logInfo("ServerApplicationFrame.actionPerformed() caught DatabaseFileWrongTypeException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_WRONG_TYPE);

	} catch (DatabaseFileNotReadableException e3) {
	    Log.logInfo("ServerApplicationFrame.actionPerformed() caught DatabaseFileNotReadableException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_READ_ERROR);

	} catch (DatabaseFileNotWritableException e4) {
	    Log.logInfo("ServerApplicationFrame.actionPerformed() caught DatabaseFileNotWritableException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_DATABASE_FILE_WRITE_ERROR);

	} catch (ServerStartException serverStartEx) {
	    Log.logInfo("ServerApplicationFrame.actionPerformed() caught ServerStartException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_SERVER_START_ERROR);

	} catch (ServerStopException serverStopEx) {
	    Log.logInfo("ServerApplicationFrame.actionPerformed() caught ServerStopException");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_SERVER_STOP_ERROR);

	} catch (Exception ex) {
	    Log.logInfo("ServerApplicationFrame.actionPerformed() caught Exception");
	    DesktopGui.showPerformedActionErrorMessage(ex);
	}
    }

    /**
     * Delegates the input parameters to {@link ServerApplicationFrameInterface#startServer(String, int)}, sets the
     * {@link #isServerStarted} to true and calls {@link #refreshControlsState()}.
     * 
     * @param path
     *            The path to the database file.
     * @param port
     *            The server port used to communicate with the client(s).
     * @throws ServerStartException
     *             Thrown if any error occurs during server start.
     */
    private void executeStartServerCommand(String path, int port) throws ServerStartException {

	this.serverApplication.startServer(path, port);

	this.isServerStarted = true;
	this.refreshControlsState();
    }

    /**
     * Delegates the call to {@link ServerApplicationFrameInterface#stopServer()}, sets the {@link #isServerStarted} to
     * false and calls {@link #refreshControlsState()}.
     * 
     * @throws ServerStopException
     *             Thrown if any error occurs during server stop.
     */
    private void executeStopServerCommand() throws ServerStopException {

	this.serverApplication.stopServer();

	this.isServerStarted = false;
	this.refreshControlsState();

    }

    /**
     * This function stops the server if it is running by calling {@link ServerApplicationFrameInterface#stopServer()}
     * first then calling {@link ServerApplicationFrameInterface#terminateApplication()}, if the server was not running
     * then only the terminate function is called. Any exceptions raised by the stopServer() method are caught and
     * logged in the log file and shown to the user by calling
     * {@link DesktopGui#showPerformedActionErrorMessage(Exception)}
     */
    private void executeTerminateServerCommand() {

	if (this.isServerStarted) {
	    try {
		this.serverApplication.stopServer();
	    } catch (Exception e) {
		Log.logInfo("Exception was raised while calling serverApplication.stopServer();" + e.getMessage());
		DesktopGui.showPerformedActionErrorMessage(e);
	    }
	}

	this.serverApplication.terminateApplication();
    }

    /**
     * Called when the frame is closed by clicking the (x) button in the frame's title bar in Microsoft Windows
     * operating system. The function calls {@link #executeTerminateServerCommand()}.
     * 
     * @param e
     *            See {@link WindowEvent}
     */
    public void windowClosing(WindowEvent e) {

	this.executeTerminateServerCommand();
    }

    /**
     * Empty implementation for {@link WindowListener#windowActivated(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowActivated(WindowEvent)}
     * 
     */
    public void windowActivated(WindowEvent e) {
    }

    /**
     * Empty implementation for {@link WindowListener#windowClosed(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowClosed(WindowEvent)}
     * 
     */
    public void windowClosed(WindowEvent e) {

    }

    /**
     * Empty implementation for {@link WindowListener#windowDeactivated(WindowEvent)}. This function does not contain
     * any code.
     * 
     * @param e
     *            See {@link WindowListener#windowDeactivated(WindowEvent)}
     * 
     */
    public void windowDeactivated(WindowEvent e) {
    }

    /**
     * Empty implementation for {@link WindowListener#windowDeiconified(WindowEvent)}. This function does not contain
     * any code.
     * 
     * @param e
     *            See {@link WindowListener#windowDeiconified(WindowEvent)}
     * 
     */
    public void windowDeiconified(WindowEvent e) {

    }

    /**
     * Empty implementation for {@link WindowListener#windowIconified(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowIconified(WindowEvent)}
     * 
     */
    public void windowIconified(WindowEvent e) {
    }

    /**
     * Empty implementation for {@link WindowListener#windowOpened(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowOpened(WindowEvent)}
     * 
     */
    public void windowOpened(WindowEvent e) {

    }
}
